<template>
  <svg ref="svg" :viewBox="`0 0 ${width} ${height}`"></svg>
</template>

<script setup>
import {onMounted, ref, watch} from 'vue'
import * as d3 from 'd3'
import cloud from 'd3-cloud'

const props = defineProps({
  words: {
    type: Array,
    default: () => [
      {text: "Vue", size: 40},
      {text: "React", size: 28}
    ]
  },
  width: {type: Number, default: 500},
  height: {type: Number, default: 500},
  colorScheme: {
    type: String,
    default: 'schemeSet2',
    validator: val => d3[val] !== undefined
  }
})

const emit = defineEmits(['wordHover'])

const svg = ref(null)

// 渲染词云
const renderWordCloud = () => {
  if (!svg.value) return
  // 清除现有内容
  d3.select(svg.value).selectAll('*').remove()
  const layout = cloud()
      .size([props.width, props.height])
      .words(props.words)
      .padding(2)
      .rotate(() => ~~(Math.random() * 6 - 3) * 30)
      .font('Impact')
      .fontSize(d => d.size)
      .on('end', words => draw(words))
  layout.start()
}

const wordSizeScale = (data) =>{
  const max = Math.max(...data.map((x) => x.size));
  const scale = 80 / max;
  for (let word of data){
    word.size *= scale;
  }
  return data
}

// 绘制词云
const draw = (words) => {
  let wordsCopy = JSON.parse(JSON.stringify(props.words));
  wordsCopy = wordsCopy.map(word => ({...word, size: word.size > 40 ? 40 : word.size}));
  wordsCopy = wordSizeScale(wordsCopy);
  const color = d3.scaleOrdinal(d3[props.colorScheme])

  const svgEl = d3.select(svg.value)
      .attr('width', props.width)
      .attr('height', props.height)

  cloud()
    .size([props.width, props.height])  // 设置词云大小
    .words(wordsCopy)                  // 传入单词数据
    .padding(5)                        // 单词间距（可调整）
    .rotate(() => (Math.random() > 0.5 ? 0 : 90))  // 随机旋转（0° 或 90°）
    .font("Impact")                    // 字体
    .fontSize(d => d.size)             // 字体大小
    .on("end", (computedWords) => {    // 计算完成后的回调
      const g = svgEl.append('g')
          .attr('transform', `translate(${props.width / 2},${props.height / 2})`);

      g.selectAll('text')
          .data(computedWords)
          .enter().append('text')
          .style('font-size', d => `${d.size}px`)
          .style('font-family', 'Impact')
          .style('fill', (d, i) => color(i))
          .attr('text-anchor', 'middle')
          .attr('transform', d => `translate(${[d.x, d.y]})rotate(${d.rotate})`)
          .text(d => d.text)
          .on('mouseover', function (event, d) {
            d3.select(this).style('font-size', `${d.size * 1.2}px`);
            emit('wordHover', d);
          })
          .on('mouseout', function (event, d) {
            d3.select(this).style('font-size', `${d.size}px`);
          });
    })
    .start();  // 开始计算布局
}

// 响应式更新
watch(() => props.words.length, () => {
  renderWordCloud()
}, {deep: true})

onMounted(() => {
  renderWordCloud()
})
</script>

<style scoped>
svg {
  display: block;
  margin: 0 auto;
  border: 1px solid #eee;
  border-radius: 4px;
  background-color: #f9f9f9;
}
</style>